﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Net;
using System.Threading;
using HtmlAgilityPack;
using System.Xml;
using System.Collections.Concurrent;

namespace SimilarTextWebCrawlerWCFService
{
    public class FigureOutWhatYouWant
    {
        const int MAX_BREDTH = 5;

        private ConcurrentDictionary<string, int> wordsAndWeights = 
            new ConcurrentDictionary<string, int>();

        private Uri startingLocation;

        private const int NUMBER_OF_WORDS_TO_RETURN = 20;

        public FigureOutWhatYouWant(Uri location)
        {
            startingLocation = location;
        }

        public string[] getWhatWasMent(string word, int maxDepth)
        {
            string[] similarWords = getSimilarWords(word);
            string[] relatedWords = CrawlerThread.getRelatedWords(word, startingLocation);
            string[] z = new string[similarWords.Length + relatedWords.Length];
            relatedWords.CopyTo(z, 0);
            similarWords.CopyTo(z, relatedWords.Length);
            // One event is used for each Fibonacci object
            ManualResetEvent[] doneEvents = new ManualResetEvent[z.Length];
            CrawlerThread[] crawlers = new CrawlerThread[z.Length];
            for (int i = 0; i < z.Length ; i++)
            {
                doneEvents[i] = new ManualResetEvent(false);
                CrawlerThread crawler = new CrawlerThread(z[i], startingLocation, doneEvents[i]);
                crawlers[i] = crawler;
                ThreadPool.QueueUserWorkItem(crawler.ThreadPoolCallback, maxDepth);
            }
            // Wait for all threads in pool to calculation...four minute timeout.
            WaitHandle.WaitAll(doneEvents, 12000);
            combineResults(crawlers);
            //After this call we should have a fully populated dictionary.
            return getTopWords<string, int>(wordsAndWeights);
        }

        private void combineResults(CrawlerThread[] crawlers)
        {
            foreach(CrawlerThread crawler in crawlers){
                mergeDictionaries(ref this.wordsAndWeights, crawler.WordsAndWeights);
            }
        }

        private void mergeDictionaries(ref ConcurrentDictionary<string, int> concurrentDictionary, 
            Dictionary<string, int> dictionary)
        {
            foreach(string key in dictionary.Keys)
            {
                concurrentDictionary.AddOrUpdate(key, dictionary[key], 
                    (k, val) => val + dictionary[key]);
            }
        }

        /// <summary>
        /// Add the key and value pair to the dictionary or increment the values
        /// that are already in the dictionary.
        /// </summary>
        /// <typeparam name="T">Any type for the key</typeparam>
        /// <param name="dictionary">A reference to a dictionary.</param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void addOrIncrement<T>(ref Dictionary<T, int> dictionary, T key, int value)
        {
            if (dictionary.ContainsKey(key))
            {
                dictionary[key] = dictionary[key] + value;
            }
            else
            {
                dictionary.Add(key, value);
            }
        }

        /// <summary>
        /// Get words that are similar to the requested word.
        /// </summary>
        /// <param name="word">Word that you are looking for similar
        /// words to.</param>
        /// <returns>An array of words that are similar to the one requested.</returns>
        private string[] getSimilarWords(string word)
        {
            SimilarWordsService.SimilarWordsWCFServiceClient client = new SimilarWordsService.SimilarWordsWCFServiceClient();
            string[] similarWords = client.SimilarWords(word);
            client.Close();
            return similarWords;
        }

        private string[] getTopWords<T, U>(IDictionary<T, U> similarWordsAndWeights) where U : IComparable
        {
            var myList = similarWordsAndWeights.ToList();

            myList.Sort((firstPair, nextPair) =>
            {
                return -firstPair.Value.CompareTo(nextPair.Value);
            }
            );
            var dictionary = myList.ToDictionary((keyItem) => keyItem.Key, (valueItem) => valueItem.Value);
            if (similarWordsAndWeights.Count < NUMBER_OF_WORDS_TO_RETURN)
            {
                return createStringArrayFromGenericList(dictionary.Keys.ToList().GetRange(0, similarWordsAndWeights.Count));
            }
            return createStringArrayFromGenericList(dictionary.Keys.ToList().GetRange(0, NUMBER_OF_WORDS_TO_RETURN));
        }

        private string[] createStringArrayFromGenericList<T>(List<T> list)
        {
            string[] output = new string[list.Count()];
            int i = 0;
            foreach (T item in list)
            {
                output[i] = item.ToString();
                i++;
            }
            return output;
        }
    }

    /// <summary>
    /// Class to download html contents instead of using the remote service.
    /// </summary>
    public class HtmlContent
    {
        string html = "";

        public HtmlContent(Uri location)
        {
            startHtmlDownload(location);
        }

        public void startHtmlDownload(Uri location)
        {
            WebClient client = new WebClient();
            client.DownloadStringCompleted +=
                new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
            client.DownloadStringAsync(location);
        }

        private void client_DownloadStringCompleted(object sender,
                                            DownloadStringCompletedEventArgs e)
        {
            html = e.Result;
        }

        public string getContent()
        {
            while (html.Equals(""))
            {
                Thread.Sleep(10);
            }
            return html;
        }
    }
}